var watchErr = minErr("watch Data");

function checkIsInDom(elem, id, path, cancelFunc, callback) {
    if (!isInDocument(elem)) {
        var nextId = $nextUid()
        cancelFunc.fnName = nextId
        $watchPB(id).unbind(path, nextId)
    } else {
        callback()
    }
}

//如何使用异步提高效率
function watchDispatcher(th, bind, data, value) {
    // var currentTmp = getCurrentTmp()
    var type = bind.type;
    if (type === "if") {
        watchIfChange(bind, data, value, th)
    } else if (type === "repeat" || type === 'foreach') {
        watchCollectionChange(bind, data, th)
    } else if (bind.nodeType === 3) {
        watchByText.call(1, bind, data, value, th)
    } else if (type === 'duplex') {
        watchByDuplex.call(1, bind, data, value, th)
    } else if (type === 'attr') {
        watchByAttr(bind, data, th)
    }
}

// function _async(fn) {
//     setTimeout(fn, 0)
// }

var $watchPB = jc.$watchPB = initPB();

function textProxy(elem, id, path, filters) {
    var response = (function(val) {
        checkIsInDom(elem, id, this.path, response, function() {
            elem.nodeValue = filterRunner(val, filters)
        })
    }).bind({
        path: path
    })
    return response
}

function watchByText(bind, data, value, th) {
    // type: "text",
    // node: node,
    // nodeType: 3,
    // value: token.value,
    // filters: filters
    var elem = bind.node,
        path = repeatPathCheck(data, bind.value),
        filters = bind.filters,
        id = th._data("id");
    $watchPB(id).bind(path, textProxy(elem, id, path, filters))
    th.listen(path, th.$div)
}

function duplexProxy(elem, id, path, filters) {
    var response = (function(val) {
        checkIsInDom(elem, id, this.path, response, function() {
            switch (elem.nodeName) {
                case "INPUT":
                    switch (elem.type) {
                        case "checkbox":
                        case "radio":
                            elem.checked = filterRunner(val, filters)
                            break;
                        default:
                            elem.value = filterRunner(val, filters)
                    }
                    break;
                case "TEXTAREA":
                case "SELECT":
                    elem.value = filterRunner(val, filters)
            }
        })
    }).bind({
        path: path
    })
    return response
}

function watchByDuplex(bind, data, value, th) {
    var elem = bind.element,
        valExpr = scanExpr(openTag + bind.value + closeTag, data)[0],
        path = repeatPathCheck(data, valExpr.value),
        filters = valExpr.filters,
        id = th._data("id")
        //dataKey = "oldValue"
    if (filters) {
        var filterExpr = dataMod.data(elem, "dupfilter")
        if (!filterExpr) {
            watchErr("if jc-duplex has filters, the node must have jc-dupfilter to declare the reverse filters of jc-duplex filters ")
        }
        filters = scanExpr(openTag + filterExpr + closeTag, data)[0].filters
    }

    //dataMod.data(elem, dataKey, value)
    $watchPB(id).bind(path, duplexProxy(elem, id, path, filters))
    th.listen(path, th.$div)
}

function attrProxy(elem, id, path, param, filters) {
    var response = (function(val) {
        checkIsInDom(elem, id, this.path, response, function() {
            if (param === 'class') {
                //TODO: 关于 class处理
            } else {
                elem.setAttribute(param, filterRunner(val, filters))
            }
        })
    }).bind({
        path: path
    })
    return response
}

function watchByAttr(bind, data, th) {
    var elem = bind.element,
        id = th._data("id"),
        valExpr = scanExpr(openTag + bind.value + closeTag, data)[0],
        path = repeatPathCheck(data, valExpr.value),
        filters = valExpr.filters,
        response = attrProxy(elem, id, path, bind.param, filters);
    $watchPB(id).bind(path, response)
    th.listen(path, th.$div)
}

function ifProxy(th, data, bind, path, filters, id) {
    var elem = bind.element,
        realElem = bind.realElem,
        param = bind.param;
    var response = (function(val) {
        val = !!filterRunner(val, filters)
        var path = this.path
        checkIsInDom(realElem, id, path, response, function() {
            var originVal = val
            if (param === 'not')
                val = !val
            var replaceElem;
            if (val) {
                if (realElem.nodeType === 8) {
                    replaceElem = elem.cloneNode(true)
                    realElem.parentNode.replaceChild(replaceElem, realElem)
                    realElem = replaceElem
                    var parentPath = getParentPath(path)
                    var parentData = dotPath(th.data(), parentPath)
                    var index = bind.value.split(".")[0]
                    data[index] = dotPath(th.data(), parentPath)

                    scanAttr(replaceElem, data, th)
                }
            } else {
                if (realElem.nodeType !== 8) {
                    replaceElem = document.createComment("if-tag")
                    realElem.parentNode.replaceChild(replaceElem, realElem)
                    realElem = replaceElem
                }
            }
        })
    }).bind({
        path: path
    })
    return response
}

function watchIfChange(bind, data, value, th) {
    var elem = bind.element,
        realElem = bind.realElem,
        valExpr = scanExpr(openTag + bind.value + closeTag, data)[0],
        filters = valExpr.filters,
        path = repeatPathCheck(data, valExpr.value),
        // dataKey = "isShow",
        param = bind.param,
        id = th._data("id");
    //dataMod.data(elem, dataKey, value)
    $watchPB(id).bind(path, ifProxy(th, data, bind, path, filters, id))
    th.listen(path, th.$div)
}


/**
 * 以element为开始，向下遍历到index同辈元素，
 * 当number被传入时，返回向下number数量的同辈元素，
 * 不number被传入时，返回该遍历到的同辈元素
 * @param  {Element|Node} element
 * @param  {Number} index
 * @param  {Number=} number
 * @return {array}
 */
function getNextSibilings(element, index, number) {

    var re = element
    while (index--) {
        re = re.nextSibling
    }
    re = [re]
    if (arguments.length === 3) {
        var el = re[0]
        while (--number) {
            el = el.nextSibling
            re.push(el)
        }
    }
    return re
}


//TODO: string 转化成正则时的处理
function collectionProxy(th, data, bind, path, id) {
    //realElem ==nodeType 8
    // var realElem = bind.realElem
    var startComment = bind.startComment,
        endComment = bind.endComment,
        response;

    if (startComment) {
        response = (function(collectionData) {
            var path = this.path,
                tpl = bind.element;
            checkIsInDom(startComment, id, path + '.length', response, function() {

                var frag = document.createDocumentFragment(),
                    parentElem = startComment.parentNode,
                    dataLength = collectionData.length,
                    param = bind.param;

                //渲染模版
                for (var i = 0; i < collectionData.length; i++) {
                    data[param] = collectionData[i]
                    if (typeof data[param] !== 'object') {
                        data.$index = i
                        data.$path = path
                    } else {
                        data[param].$index = i
                        data[param].$path = path
                    }
                    frag.appendChild(tpl.cloneNode(true))
                    scanAttr(frag.lastChild, data, th)
                }
                while (startComment.nextSibling !== endComment) {
                    parentElem.removeChild(startComment.nextSibling)
                }
                parentElem.insertBefore(frag, endComment)

            })
        }).bind({
            path: path
        })
    } else {
        //foreach

        response = (function(collectionData) {

            var frag = document.createDocumentFragment(),
                parentElem = bind.element,
                path = this.path,
                param = bind.param,
                tpl = bind.tpl;
            for (var i = 0; i < collectionData.length; i++) {
                data[param] = collectionData[i]
                if (typeof data[param] !== 'object') {
                    data.$index = i
                    data.$path = path
                } else {
                    data[param].$index = i
                    data[param].$path = path
                }
                for (var j = 0; j < tpl.length; j++) {
                    frag.appendChild(tpl[j].cloneNode(true))
                    scanAttr(frag.lastChild, data, th)
                }
            }
            parentElem.innerHTML = ""
            parentElem.appendChild(frag)
        }).bind({
            path: path
        })
    }
    return response
}
/**
 * 观察collection length 变化，触发页面绑定变话
 * @param  {object} bind
 * @param  {object} data
 * @param  {object} th
 */
function watchCollectionChange(bind, data, th) {
    // type: type,
    // param: param,
    // element: elem,
    // name: match[0],
    // value: attr.value,
    // realElem..
    var param = bind.param,
        tpl = bind.element,
        id = th._data("id");
    var path = data.$path || data[param].$path;

    $watchPB(id).bind(path + '.length', collectionProxy(th, data, bind, path, id))
    th.listen(path, th.$div)
    // $watchPB(currentTmp._data("id"))    
}